﻿using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using theeg.DependencyAnalysis.CommonAddin.Models;

namespace CommonTestsLibrary
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var rules = new List<RuleDescriptor>();
            AppendDependencyRules(rules);
            AppendCouplingRules(rules);
            AppendTypeQualityRules(rules);
            AppendMethodQualityRules(rules);
            SerializeCollection(rules);
        }

        private static void SerializeCollection(List<RuleDescriptor> rules)
        {
            var fileStream = new FileStream("CommonTests.serialized", FileMode.CreateNew);
            var serializer = new DataContractSerializer(typeof(RuleDescriptor[]), null, int.MaxValue, true, true, null);
            serializer.WriteObject(fileStream, rules.ToArray());
        }

        private static void AppendTypeQualityRules(ICollection<RuleDescriptor> rules)
        {
            var rule = new RuleDescriptor
            {
                Name = "Types are coherant",
                Category = "Type quality"
            };
            rule.Source = @"
            Verifier.VerifyThat<TypeDescriptor>((x, state) =>
                                                    {
                                                        var c = Metrics.LackOfCohesion(x);
                                                        var fields = Metrics.NumberOfFields(x);
                                                        var methods = Metrics.NumberOfMethods(x);
                                                        if (c > 1.0 && fields > 10 && methods > 10)
                                                        {
                                                            state.Message = string.Format(
                                                                ""cohesion={0:0.00} fields={1} methods={2}"",
                                                                 c, fields, methods);
                                                            return false;
                                                        }
                                                        return true;
                                                    }, Filters.MyTypes);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Type Cyclomatic complexity at most 200",
                Category = "Type quality"
            };
            rule.Source = @"
            Verifier.VerifyThat<TypeDescriptor>((x, state) =>
            {
                var c = Metrics.CyclomaticComplexity(x);
                if (c > 200)
                {
                    state.Message = string.Format(""'cyclomatic complexity={0} > 200'"", c);
                    return false;
                }
                return true;
            }, Filters.MyTypes);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Type Instructions at most 2000",
                Category = "Type quality"
            };
            rule.Source = @"
            Verifier.VerifyThat((x, state) =>
            {
                var c = Metrics.NumberOfInstructions(x);
                if (c > 2000)
                {
                    state.Message = string.Format(""'instructions={0} > 2000'"", c);
                    return false;
                }
                return true;
            }, Filters.MyTypes);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Number of methods at most 30",
                Category = "Type quality"
            };
            rule.Source = @"
            Verifier.VerifyThat((x, state) =>
            {
                var c = Metrics.NumberOfMethods(x);
                if (c > 30)
                {
                    state.Message = string.Format(""'methods={0} > 30'"", c);
                    return false;
                }
                return true;
            }, Filters.MyTypes);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Number of fields at most 30",
                Category = "Type quality"
            };
            rule.Source = @"
            Verifier.VerifyThat((x, state) =>
            {
                if ((x.AbstractionType | AbstractionType.Descriptive) != 0)
                {
                    return true;
                }
                var c = Metrics.NumberOfFields(x);
                if (c > 30)
                {
                    state.Message = string.Format(""'fields={0} > 30'"", c);
                    return false;
                }
                return true;
            }, Filters.MyTypes);";
            rules.Add(rule);
        }

        private static void AppendMethodQualityRules(ICollection<RuleDescriptor> rules)
        {
            var rule = new RuleDescriptor
            {
                Name = "Member Cyclomatic complexity is at most 20",
                Category = "Method quality"
            };
            rule.Source = @"
            Verifier.VerifyThat<MemberDescriptor>((x, state) =>
                {
                    var c = Metrics.CyclomaticComplexity(x);
                    if (c > 20)
                    {
                        state.Message = string.Format(""'cyclomatic complexity={0} > 20'"", c);
                        return false;
                    }
                    return true;
                }, Filters.MyMembers);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Member Instructions at most 200",
                Category = "Method quality"
            };
            rule.Source = @"
            Verifier.VerifyThat<MemberDescriptor>((x, state) =>
            {
                var c = Metrics.NumberOfInstructions(x);
                if (c > 200)
                {
                    state.Message = string.Format(""'instructions={0} > 200'"", c);
                    return false;
                }
                return true;
            }, Filters.MyMembers);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Nesting depth at most 6",
                Category = "Method quality"
            };
            rule.Source = @"
            Verifier.VerifyThat<MemberDescriptor>((x, state) =>
            {
                var c = Metrics.NestingDepth(x);
                if (c > 6)
                {
                    state.Message = string.Format(""'nesting depth={0} > 6'"", c);
                    return false;
                }
                return true;
            }, Filters.MyMembers);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Parameters at most 3",
                Category = "Method quality"
            };
            rule.Source = @"
            Verifier.VerifyThat<MemberDescriptor>((x, state) =>
            {
                var c = Metrics.NumberOfParameters(x);
                if (c > 3 && !x.IsOverride && !x.IsDefinedByInterface)
                {
                    state.Message = string.Format(""'number of parameters={0} > 3'"", c);
                    return false;
                }
                return true;
            }, Filters.MyMembers);";
            rules.Add(rule);
        }

        private static void AppendDependencyRules(ICollection<RuleDescriptor> rules)
        {
            var rule = new RuleDescriptor
            {
                Name = "Acyclic dependencies",
                Category = "Dependencies"
            };
            rule.Source = @"
            var cycles = Principles.AcyclicDependencies(Model);
            StringBuilder sb = new StringBuilder();
            foreach (var tuple in cycles)
            {
                foreach (var descriptor in tuple)
                {
                    sb.Append(descriptor.FullName);
                    sb.Append("" - "");
                }
                sb.Append(""\n"");
            }

            Assert.AreEqual(string.Empty, sb.ToString());";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Allowed foreign assemblies",
                Category = "Dependencies"
            };
            rule.Source = @"
            string[] allowedAssemblies = new string[] {  };
            Verifier.VerifyForeignModules(allowedAssemblies);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Composition with disposable member",
                Category = "Dependencies"
            };
            rule.Source = @"
            var idisposable = DescriptorBuilder.BuildTypeDescriptor(typeof(IDisposable));
            Verifier.VerifyThat<TypeDescriptor>((x, state) =>
            {
                var fields = (from y in x.Members where y.MemberType == MemberType.Field select y).ToArray();
                foreach (var field in fields)
                {
                    var firstReference = Model.DependenciesPerMemberOut[field].FirstOrDefault();
                    if (firstReference == null)
                    {
                        continue;
                    }
                    var fieldType = firstReference.Target.ImplementingType;
                    if (Equals(fieldType, idisposable) ||
                        fieldType.InheritedTypes != null && fieldType.InheritedTypes.Contains(idisposable))
                    {
                        if (x.InheritedTypes != null && !x.InheritedTypes.Contains(idisposable))
                        {
                            state.Message = ""must implement IDisposable \"""" + field.Name + ""\"""";
                            return false;
                        }
                        break;
                    }
                }
                return true;
            }, x => Filters.MyTypes(x) && x.AbstractionType != AbstractionType.Static);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No octopus (at most 7 referenced self defined class types)",
                Category = "Dependencies"
            };
            rule.Source = @"
            var dependenciesPerTypeOut = Model.DependenciesPerTypeOut;
            Verifier.VerifyThat<TypeDescriptor>((x, state) =>
            {
                if (x.AbstractionType == AbstractionType.Descriptive || x.IsDataObject)
                {
                    return true;
                }
                var types = VerifierUtilities.AggregateDependenciesToTypes(dependenciesPerTypeOut[x], false);
                int count =
                    types.Count(
                        y => !y.IsGenerated && y.Submodule.Module.IsPartOfProject && !y.IsEnum && !y.IsDataObject && y.AbstractionType == AbstractionType.Concrete);
                state.Message = """" + count;
                return count < 8;
            }, Filters.MyTypes);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No modules in zone of pain",
                Category = "Dependencies"
            };
            rule.Source = @"
            Verifier.VerifyThat<ModuleDescriptor>((x, state) =>
            {
                var d = Metrics.DistanceFromMainSequence(x);
                if (d < -0.7)
                {
                    state.Message = string.Format(""0:0.00}"", d);
                    return false;
                }
                return true;
            }, Filters.MyModules);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No strongly coupled class",
                Category = "Dependencies"
            };
            rule.Source = @"
Predicate<TypeDescriptor> concreteClass = x => !(x.IsGenerated || !x.Submodule.Module.IsPartOfProject || x.IsDataObject || x.AbstractionType != AbstractionType.Concrete || (x.Type.BaseType != null && (x.Type.BaseType == typeof(MulticastDelegate) || typeof(Exception).IsAssignableFrom(x.Type))));

var dependenciesPerType = Model.DependenciesPerType;
Verifier.VerifyThat((x, state) =>
{
    var types = VerifierUtilities.AggregateDependenciesToTypes(dependenciesPerType[x], true);
    int count = types.Count(y => !y.IsGenerated && y.Submodule.Module.IsPartOfProject);
    state.Message = string.Empty + count;
    return count < 5;
},
concreteClass);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No modules in zone of uselessness",
                Category = "Dependencies"
            };
            rule.Source = @"
            Verifier.VerifyThat<ModuleDescriptor>((x, state) =>
                                                      {
                                                          var d = Metrics.DistanceFromMainSequence(x);
                                                          if (d > 0.7)
                                                          {
                                                              state.Message = string.Format(""0:0.00}"", d);
                                                              return false;
                                                          }
                                                          return true;
                                                      }, Filters.MyModules);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No useless abstract class",
                Category = "Dependencies"
            };
            rule.Source = @"
            var dependenciesPerType = Model.DependenciesPerType;
            Verifier.VerifyThat<TypeDescriptor>((x, state) =>
            {
                if (x.AbstractionType != AbstractionType.Interface &&
                     x.AbstractionType != AbstractionType.Abstract)
                {
                    return true;
                }
                var types = VerifierUtilities.AggregateDependenciesToTypes(
                    dependenciesPerType[x], true);
                int count = types.Count(
                        y => !y.IsGenerated && y.Submodule.Module.IsPartOfProject);
                count += x.ImplementingTypes == null ? 0 : x.ImplementingTypes.Count * 2;
                state.Message = string.Empty + count;
                return count > 1;
            }, Filters.MyTypes);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "Stable dependencies",
                Category = "Dependencies"
            };
            rule.Source = @"
            var violation = Principles.StableDependencies<ModuleDescriptor>(Model);
            StringBuilder sb = new StringBuilder();
            foreach (var tuple in violation)
            {
                sb.Append(string.Format(""{0} ({1}) -> {2} ({3})\n"", tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4));
            }

            Assert.AreEqual(string.Empty, sb.ToString());";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No method marked pure modifies state",
                Category = "Dependencies"
            };
            rule.Source = @"
            var pureAttribue = DescriptorBuilder.BuildTypeDescriptor(typeof (System.Diagnostics.Contracts.PureAttribute));
            Verifier.VerifyThat<MemberDescriptor>((x, state) =>
            {
                if (x.MemberType != MemberType.Method)
                {
                    return true;
                }
                if(!x.IsPure && Verifier.RefersTo(x, pureAttribue))
                {
                    var attribution = Model.DependenciesPerMemberOut[x].FirstOrDefault(
                        y => y.ReferenceType == ReferenceType.Attribute && Equals(pureAttribue, y.Target.ImplementingType));
                    if (attribution != null)
                    {
                        state.Message = "" + x.Purity;
                        return false;
                    }
                }
                return true;
            }, Filters.MyMembers);";
            rules.Add(rule);
        }

        private static void AppendCouplingRules(ICollection<RuleDescriptor> rules)
        {
            var rule = new RuleDescriptor
            {
                Name = "No static events defined",
                Category = "Common coupling"
            };
            rule.Source = @"
            Verifier.VerifyThat<MemberDescriptor>(
                (x, state) => !x.Name.StartsWith(""add_"") && !x.Name.StartsWith(""remove_""),
                x => Filters.MyMembers(x) && x.IsStatic);";
            rules.Add(rule);

            rule = new RuleDescriptor
            {
                Name = "No subscription to static events",
                Category = "Common coupling"
            };
            rule.Source = @"
            Func<DependencyDescriptor, bool> dependenciesContainStaticEvent =
                y => y.Target.IsStatic && (y.Target.Name.StartsWith(""add_"") || y.Target.Name.StartsWith(""remove_""));

            Verifier.VerifyThat<MemberDescriptor>(
                (x, state) =>
                {
                    var firstChance = Model.DependenciesPerMemberOut[x].FirstOrDefault(dependenciesContainStaticEvent);
                    if (firstChance != null)
                    {
                        state.Message = firstChance.Target.FullName;
                        return false;
                    }
                    return true;
                },
                x => Filters.MyMembers(x));";
            rules.Add(rule);
        }
    }
}
